<!DOCTYPE html>
<title>CSS Anchor Position Test: invalid at computed-value time</title>
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-valid">
<link rel="help" href="https://drafts.csswg.org/css-anchor-position-1/#anchor-size-valid">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
  :root {
    --top: top;
  }
  #cb {
    position: relative;
    width: 200px;
    height: 200px;
    border: 1px solid black;
  }

  #anchor {
    anchor-name: --a;
    position: absolute;
    width: 50px;
    height: 40px;
    left: 75px;
    top: 75px;
    background: coral;
  }

  #main > div, #ref {
    position: absolute;
    background: seagreen;
  }

  #ref {
    inset: unset;
    width: unset;
    height: unset;
    min-width: unset;
    min-height: unset;
    max-width: unset;
    max-height: unset;
  }

</style>
<div id=cb>
  <div id=anchor></div>
  <div id=main></div>
  <div id=ref>X</div>
</div>
<script>

// Append <div>X</div> to `container`, and remove it again once the test (`t`)
// is finished.
function createTarget(t, container) {
  t.add_cleanup(() => { container.replaceChildren(); });
  let target = document.createElement('div');
  target.textContent = 'X';
  container.append(target);
  return target;
}

// First, some sanity checks to verify that the anchor etc is set up correctly,
// and that anchor() queries can produce results if done correctly.

test((t) => {
  let target = createTarget(t, main);
  target.style = `
    position-anchor: --a;
    left:anchor(right);
    top:anchor(top);
    width:anchor-size(width);
    height:anchor-size(height);
  `;
  let cs = getComputedStyle(target);
  assert_equals(cs.left, '125px');
  assert_equals(cs.top, '75px');
  assert_equals(cs.width, '50px');
  assert_equals(cs.height, '40px');
}, 'Element can be anchor positioned');

test((t) => {
  let target = createTarget(t, main);
  target.style = `
    /* No position-anchor here */
    left:anchor(right, 17px);
    top:anchor(top, 18px);
    width:anchor-size(width, 42px);
    height:anchor-size(height, 43px);
  `;
  let cs = getComputedStyle(target);
  assert_equals(cs.left, '17px');
  assert_equals(cs.top, '18px');
  assert_equals(cs.width, '42px');
  assert_equals(cs.height, '43px');
}, 'Element can use <length> fallback if present');

// Now test that any invalid anchor*() behaves as invalid at computed-value
// time if there's no fallback specified.

// Check that an anchored element with the specified style has the same
// computed insets and sizing as the reference element (#ref), i.e. all
// insets and sizing properties behave as 'unset'.
function test_ref(style, description) {
  test((t) => {
    let target = createTarget(t, main);
    target.style = style;
    let cs = getComputedStyle(target);
    let ref_cs = getComputedStyle(ref);
    assert_equals(cs.top, ref_cs.top, 'top');
    assert_equals(cs.left, ref_cs.left, 'left');
    assert_equals(cs.right, ref_cs.right, 'right');
    assert_equals(cs.bottom, ref_cs.bottom, 'bottom');
    assert_equals(cs.width, ref_cs.width, 'width');
    assert_equals(cs.height, ref_cs.height, 'height');
    assert_equals(cs.minWidth, ref_cs.minWidth, 'minWidth');
    assert_equals(cs.minHeight, ref_cs.minHeight, 'minHeight');
    assert_equals(cs.maxWidth, ref_cs.maxWidth, 'maxWidth');
    assert_equals(cs.maxHeight, ref_cs.maxHeight, 'maxHeight');
  }, `Invalid anchor function, ${description}`);
}

// No default anchor (position-anchor):
test_ref('left:anchor(left)', 'left');
test_ref('right:anchor(right)', 'right');
test_ref('bottom:anchor(bottom)', 'bottom');
test_ref('top:anchor(top)', 'top');
test_ref('width:anchor-size(width)', 'width');
test_ref('height:anchor-size(height)', 'height');
test_ref('min-width:anchor-size(width)', 'min-width');
test_ref('min-height:anchor-size(height)', 'min-height');
test_ref('max-width:anchor-size(width)', 'max-width');
test_ref('max-height:anchor-size(height)', 'max-height');

// Unknown anchor reference:
test_ref('left:anchor(--unknown left)', '--unknown left');
test_ref('width:anchor-size(--unknown width)', '--unknown width');

// Wrong axis;
test_ref('left:anchor(--a top)', 'cross-axis query (vertical)');
test_ref('top:anchor(--a left)', ' cross-axis query (horizontal)');

// Wrong query for the given property:
test_ref('top:anchor-size(--a width)', 'anchor-size() in inset');
test_ref('width:anchor(--a left)', 'anchor() in sizing property');

// Invalid anchor*() deeper within calc():
test_ref('left:calc(anchor(left) + 10px)', 'nested left');
test_ref('right:calc(anchor(right) + 10px)', 'nested right');
test_ref('bottom:calc(anchor(bottom) + 10px)', 'nested bottom');
test_ref('top:calc(anchor(top) + 10px)', 'nested top');
test_ref('min-width:calc(anchor-size(width) + 10px)', 'nested min-width');
test_ref('min-height:calc(anchor-size(height) + 10px)', 'nested min-height');
test_ref('max-width:calc(anchor-size(width) + 10px)', 'nested max-width');
test_ref('max-height:calc(anchor-size(height) + 10px)', 'nested max-height');

// Invalid anchor*() within fallback:
test_ref('top:anchor(top, anchor(--unknown top))', 'invalid anchor() in fallback');
test_ref('width:anchor-size(width, anchor-size(--unknown width))', 'invalid anchor-size() in fallback');

// Non-calc() functions:
test_ref('top:min(10px, anchor(top))', 'min()');
test_ref('top:max(10px, anchor(top))', 'max()');
test_ref('top:abs(anchor(top) - 100px)', 'abs()');
test_ref('top:calc(sign(anchor(top) - 100px) * 20px)', 'sign()');

// var():
test_ref('top:anchor(var(--top))', 'anchor(var())');
test_ref('top:anchor(var(--unknown, top))', 'anchor(unknown var()) (fallback)');
test_ref('top:anchor(var(--unknown))', 'anchor(unknown var()) (no fallback)');

// Reverting to an invalid anchor():
test((t) => {
  let target = createTarget(t, main);
  target.setAttribute('id', 'target');

  let css = document.createElement('style');
  css.textContent = `
    @layer base {
      #target {
        top: anchor(top); /* Invalid */
        color: green;
      }
    }
    #target {
      top: revert-layer; /* Reverts to 'base'. */
    }
  `;

  t.add_cleanup(() => { css.remove(); })
  cb.append(css);

  let cs = getComputedStyle(target);
  let ref_cs = getComputedStyle(ref);
  // The color check verifies that the rule is applied at all.
  assert_equals(cs.color, 'rgb(0, 128, 0)');
  assert_equals(cs.top, ref_cs.top);
}, 'Revert to invalid anchor()');

// Using <try-tactic> to flip to an invalid anchor():
test((t) => {
  let target = createTarget(t, main);
  target.setAttribute('id', 'target');

  let css = document.createElement('style');
  css.textContent = `
    @position-try --pt {
      /* Undo force overflow, and also use this value to check that
         the rule is applied at all. */
      left: 10px;

      /* Invalid. Becomes bottom:anchor(bottom) (also invalid)
         after flip-block. */
      top: anchor(top);
    }

    #target {
      left: 9999px; /* Force overflow. */
      position-try-fallbacks: --pt flip-block;
    }
  `;

  t.add_cleanup(() => { css.remove(); })
  cb.append(css);

  let cs = getComputedStyle(target);
  let ref_cs = getComputedStyle(ref);
  assert_equals(cs.left, '10px', 'left');
  // 'right' is not important in this test.

  assert_equals(cs.top, ref_cs.top, 'top');
  assert_equals(cs.bottom, ref_cs.bottom, 'bottom');
}, 'Flip to invalid anchor()');

</script>

